Checking if a model is valid is pretty an automatic behavior at this point. Whenever a request expects a model, we need to make sure that the validation is good, and we use ModelState.IsValid
for that.
But that becomes boring, most of the time (if not always) we redirect to the same page, another page or return a bad request in case of an API.
Why not use an Action Filter to make this easier?
How model validation works
Setup
Imagine you have this ViewModel,:
public class CodeViewModel
{
[Required]
public string UserId { get; set; }
[Required]
[StringLength(40, MinimumLength = 40)]
public string Code { get; set; }
}
This action in your controller:
[HttpGet]
public IActionResult ConfirmEmail(CodeViewModel model)
{
// Logic here
return View();
}
Model Binding
Model binding is a step that happens after ASP.NET Core receives an HTTP request and has figured out which action/controller (or razor page) that will handle it.
It simply tries to bind the route data (e.g. query data if it’s a GET request and body data if it’s a POST) and checks whether the parameters you’re waiting for (either direct parameters or objects) are what was received in the request.
Model binding errors are mostly casting/conversion errors. For example you’re waiting for an integer userId
but you receive a string containing ”test”
.
Model validation
Now when model binding is done, the model validation starts (if defined). ASP.NET Core looks at your ViewModel, checking if it has any validation attributes and applies them. Some of the much used attributes are:
Name | Description |
---|---|
Required | A value for this property is required |
DataType | Specifies the type of the property, e.g. EmailAddress |
Range | Specifies the range of the property, works on numbers |
Compare | Compares the value of the property with another one, used to confirm passwords/emails |
ModelState
The Controller
class provides a ModelState
that you can check to see if the model binding and/or model validation succeeded or not, with the list of errors generated.
if (!ModelState.IsValid)
{
// Do something about it!
// Usually return the user to the same page
// while showing the errors.
}
Using an ActionFilter to check the ModelState for you
After a couple of years working with ASP.NET Core, I got bored of having to check if the ModelState is valid in almost every request, so I searched a bit and found out that Action Filters.
Action filters execute after model validation, so it’s very handy as we can access the ModelState while working with them.
IfModelIsInvalid attribute
I ended up creating a IfModelIsInvalid
attribute that checks if the ModelState is invalid, if yes, it redirects to either an action/controller or a page (in case you’re working with a mixed project that includes Razor Pages).
public class IfModelIsInvalidAttribute : ActionFilterAttribute
{
#region Properties
public string RedirectToController { get; set; }
public string RedirectToAction { get; set; }
public string RedirectToPage { get; set; }
#endregion
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new RedirectToRouteResult(ConstructRouteValueDictionary());
}
}
#region Private Methods
private RouteValueDictionary ConstructRouteValueDictionary()
{
var dict = new RouteValueDictionary();
if (!string.IsNullOrWhiteSpace(RedirectToPage))
{
dict.Add("page", RedirectToPage);
}
// Assuming RedirectToController & RedirectToAction are set
else
{
dict.Add("controller", RedirectToController);
dict.Add("action", RedirectToAction);
}
return dict;
}
#endregion
}
The code is pretty simple and straight forward.
We check context.ModelState.IsValid
to see if something failed, if yes, we redirect the user to either an action/controller or a razor page.
To set the result of the request to a redirect in an Action Filter, we have to create a RedirectToRouteResuilt
that contains a RouteValueDictionary
containing where we want to redirect.
Usage
Lastly, the time comes where you forget about checking the ModelState in every request and use the attribute:
[HttpGet]
[IfModelIsInvalid(RedirectToAction = "Index", RedirectToController = "Account")]
public IActionResult ConfirmEmail(CodeViewModel model)
{
// Logic here
return View();
}
If something fails on the model validation, the user will be redirected to the Index
action of the Account
controller.
This results in a pretty readable solution that saves us the trouble to write the same code on every request.
Of course, the Action Filter can be personalized however you like!